package com.decent.manager.system.auth.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.decent.common.bo.MessageBean;
import com.decent.common.constant.Constants;
import com.decent.common.entity.manager.SystemMenu;
import com.decent.common.entity.manager.SystemRole;
import com.decent.common.entity.manager.SystemUser;
import com.decent.common.enums.AuthExceptionEnum;
import com.decent.common.enums.ErrorCodeEnum;
import com.decent.common.enums.manager.UserTypeEnum;
import com.decent.common.exception.ErrorCodeException;
import com.decent.common.util.http.GetIpUtil;
import com.decent.manager.config.security.SpringSecurityConfig;
import com.decent.manager.constant.ManagerConstants;
import com.decent.manager.system.auth.jwt.JwtPayLoad;
import com.decent.manager.system.auth.jwt.JwtTokenUtil;
import com.decent.manager.system.auth.service.AuthService;
import com.decent.manager.system.auth.service.CleanAuthService;
import com.decent.manager.system.auth.vo.LoginData;
import com.decent.manager.system.auth.vo.LoginMenuTreeNode;
import com.decent.manager.system.auth.vo.LoginUser;
import com.decent.manager.system.cache.LoginUserCache;
import com.decent.manager.system.service.SystemMenuService;
import com.decent.manager.system.service.SystemRoleService;
import com.decent.manager.system.service.SystemUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 认证相关service实现类
 *
 * @author wangyan
 **/
@Slf4j
@Service
public class AuthServiceImpl implements AuthService, UserDetailsService {
    @Resource
    private SystemUserService systemUserService;
    @Resource
    private SystemRoleService systemRoleService;
    @Resource
    private HttpServletRequest httpServletRequest;
    @Resource
    private LoginUserCache<LoginUser> loginUserCache;
    @Resource
    private SystemMenuService systemMenuService;
    @Resource
    private CleanAuthService cleanAuthService;

    /**
     * 登录鉴权时从数据库加载用户信息
     *
     * @param account 用户名
     * @return 用户信息
     * @throws UsernameNotFoundException 异常
     */
    @Override
    public LoginUser loadUserByUsername(String account) {
        SystemUser user = systemUserService.queryUserByMobile(account);
        if (!Objects.nonNull(user)) {
            throw new UsernameNotFoundException(AuthExceptionEnum.ACCOUNT_PWD_ERROR.getMessage());
        }
        LoginUser loginUser = new LoginUser();
        BeanUtil.copyProperties(user, loginUser);
        return loginUser;
    }

    @Override
    public String getTokenFromRequest(HttpServletRequest request) {
        String authToken = request.getHeader(ManagerConstants.AUTHORIZATION);
        if (ObjectUtil.isEmpty(authToken) || ManagerConstants.UNDEFINED.equals(authToken)) {
            return null;
        } else {
            //token不是以Bearer打头，则响应回格式不正确
            if (!authToken.startsWith(ManagerConstants.TOKEN_TYPE_BEARER)) {
                throw new ErrorCodeException(ErrorCodeEnum.NO, AuthExceptionEnum.NOT_VALID_TOKEN_TYPE.getMessage());
            }
            try {
                authToken = authToken.substring(ManagerConstants.TOKEN_TYPE_BEARER.length() + 1);
            } catch (StringIndexOutOfBoundsException e) {
                throw new ErrorCodeException(ErrorCodeEnum.NO, AuthExceptionEnum.NOT_VALID_TOKEN_TYPE.getMessage());
            }
        }
        return authToken;
    }

    @Override
    public LoginUser getLoginUserByToken(String token) {
        // 校验token，错误则抛异常
        this.checkToken(token);
        // 根据token获取JwtPayLoad部分
        JwtPayLoad jwtPayLoad = JwtTokenUtil.getJwtPayLoad(token);
        // 从redis缓存中获取登录用户
        LoginUser cacheObject = loginUserCache.get(jwtPayLoad.getUuid());
        // 用户不存在则表示登录已过期
        if (ObjectUtil.isEmpty(cacheObject)) {
            return null;
        }
        // 刷新缓存，在登录过期前活动的用户自动刷新缓存时间
        this.cacheLoginUser(jwtPayLoad, cacheObject);
        //返回用户
        return cacheObject;
    }

    @Override
    public MessageBean logout() {
        //获取token
        String token = this.getTokenFromRequest(httpServletRequest);
        //如果token为空直接返回
        if (ObjectUtil.isEmpty(token)) {
            return new MessageBean(ErrorCodeEnum.OK);
        }
        //校验token，错误则抛异常，待确定
        this.checkToken(token);
        //根据token获取JwtPayLoad部分
        JwtPayLoad jwtPayLoad = JwtTokenUtil.getJwtPayLoad(token);
        cleanAuthService.clearCurrentUser(jwtPayLoad);
        return new MessageBean(ErrorCodeEnum.OK);
    }

    @Override
    public void setSpringSecurityContextAuthentication(LoginUser loginUser) {
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                new UsernamePasswordAuthenticationToken(
                        loginUser,
                        null,
                        loginUser.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
    }

    @Override
    public Authentication getAuthentication() {
        return SecurityContextHolder.getContext().getAuthentication();
    }

    @Override
    public void checkToken(String token) {
        // 校验token是否正确
        Boolean tokenCorrect = JwtTokenUtil.checkToken(token);
        if (!tokenCorrect) {
            throw new ErrorCodeException(ErrorCodeEnum.NO, AuthExceptionEnum.REQUEST_TOKEN_ERROR.getMessage());
        }
        //校验token是否失效
        Boolean tokenExpired = JwtTokenUtil.isTokenExpired(token);
        if (tokenExpired) {
            throw new ErrorCodeException(AuthExceptionEnum.LOGIN_EXPIRED);
        }
    }

    @Override
    public LoginUser genSysLoginUser(SystemUser sysUser) {
        LoginUser loginUser = new LoginUser();
        BeanUtil.copyProperties(sysUser, loginUser);
        this.fillLoginUserInfo(loginUser);
        return loginUser;
    }

    /**
     * 构建登录用户信息
     *
     * @param loginUser 当前登录用户
     */
    private void fillLoginUserInfo(LoginUser loginUser) {
        loginUser.setLastLoginIp(GetIpUtil.getIp());
        loginUser.setLastLoginTime(LocalDateTime.now());
        Long userId = loginUser.getId();

        // 角色信息
        SystemRole role = systemRoleService.getUserRole(userId);
        if (role == null) {
            throw new ErrorCodeException(ErrorCodeEnum.NO, "当前用户未配置登录角色");
        }
        loginUser.setRole(role);
        List<SystemMenu> roleMenus = systemRoleService.getRoleMenus(UserTypeEnum.SUPER_ADMIN == loginUser.getUserType() ?
                null : role.getId());
        // 页面资源访问路由
        Set<String> permissions = roleMenus.stream().map(menu -> routerConvertToPermission(menu.getRouter()))
                .collect(Collectors.toSet());
        // 页面配套的查询按钮后台路由
        permissions.addAll(roleMenus.stream().filter(menu -> StrUtil.isNotBlank(menu.getQueryRoute()))
                .map(menu -> routerConvertToPermission(menu.getQueryRoute())).collect(Collectors.toList()));
        // 基础权限路由
        permissions.addAll(SpringSecurityConfig.BASIC_PERMISSION_URL_PATTERNS.stream()
                .map(this::routerConvertToPermission).collect(Collectors.toList()));
        loginUser.setPermissions(permissions);

        //AntDesign菜单信息，根据人获取，用于登录后展示菜单树，默认获取默认激活的系统的菜单
        List<LoginMenuTreeNode> loginMenuTreeNodes = systemMenuService.convertSystemMenuToLoginMenu(roleMenus);
        loginUser.setMenus(loginMenuTreeNodes);
    }

    /**
     * 路由地址转换成资源权限
     *
     * @param router 路由地址
     * @return 权限信息
     */
    private String routerConvertToPermission(String router) {
        String removeStart = StrUtil.removePrefix(router, Constants.LEFT_DIVIDE);
        return StrUtil.replace(removeStart, Constants.LEFT_DIVIDE, Constants.COLON);
    }

    /**
     * 缓存token与登录用户信息对应, 2个小时
     *
     * @param loginUser  用户
     * @param jwtPayLoad 用户
     */
    @Override
    public void cacheLoginUser(JwtPayLoad jwtPayLoad, LoginUser loginUser) {
        loginUserCache.put(jwtPayLoad.getUuid(), loginUser, 2 * 60 * 60L);
    }

    /**
     * 根据系统用户构造用户登陆信息（含导航）
     *
     * @param sysUser 系统用户
     * @return 用户信息
     **/
    @Override
    public LoginData getLoginUserUpToDate(SystemUser sysUser) {
        LoginData loginUser = new LoginData();
        loginUser.setName(sysUser.getUserName());
        // 角色信息
        SystemRole role = systemRoleService.getUserRole(sysUser.getId());
        if (role == null) {
            throw new ErrorCodeException(ErrorCodeEnum.NO, "当前用户未配置登录角色");
        }
        loginUser.setRoleName(role.getRoleName());
        List<SystemMenu> roleMenus = systemRoleService.getRoleMenus(UserTypeEnum.SUPER_ADMIN == sysUser.getUserType() ?
                null : role.getId());
        LoginData.LoginPermissionVO permission = loginUser.getPermissionTreeVO();
        permission.setTotal(roleMenus.size());
        permission.setRows(roleMenus.stream()
                .map(menu -> {
                    LoginData.NavigatorVO navigatorVO = new LoginData.NavigatorVO();
                    navigatorVO.setId(menu.getId());
                    navigatorVO.setFunctionName(menu.getMenuName());
                    navigatorVO.setType(menu.getMenuType());
                    navigatorVO.setParentId(menu.getPid());
                    navigatorVO.setSort(menu.getMenuSort());
                    navigatorVO.setUrl(menu.getRouter());
                    navigatorVO.setIcon(menu.getIcon());
                    navigatorVO.setSelected(true);
                    navigatorVO.setVisibility(menu.getVisibility());
                    return navigatorVO;
                }).collect(Collectors.toList()));
        loginUser.setPermissionTreeVO(permission);
        return loginUser;
    }
}
